package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"

	bpf "github.com/aquasecurity/libbpfgo"
)

// 定义全局变量 hostPidns
var hostPidns uint64

// readPidns 读取当前进程的 PID 命名空间标识符,ll /proc/self/ns/pid
func readPidNs() {
	// 读取 /proc/self/ns/pid 的符号链接内容
	n, err := os.Readlink("/proc/self/ns/pid")
	if err != nil {
		fmt.Printf("Error reading /proc/self/ns/pid: %v\n", err)
		return
	}
	// 分割字符串，提取数字部分
	parts := strings.Split(n, "[")
	if len(parts) < 2 {
		fmt.Println("Invalid format in /proc/self/ns/pid")
		return
	}

	numStr := strings.TrimSuffix(parts[1], "]")

	// 将数字部分转换为无符号整数
	hostPidns, err = strconv.ParseUint(numStr, 10, 64)
	if err != nil {
		fmt.Printf("Error parsing PID namespace: %v\n", err)
		return
	}
}


type cdata struct {
	HostPid  uint32
	HostPpid uint32
	PidNs  uint64
	Comm [16]byte
	Type  uint32
}

//var reContainerId = regexp.MustCompile(`/docker/([0-9a-f]{64})$`)

func (c cdata) commName() string {
	return string(bytes.TrimRight(c.Comm[:], "\x00"))
}

func resizeMap(module *bpf.Module, name string, size uint32) error {
	m, err := module.GetMap(name)
	if err != nil {
		return err
	}

	if err = m.Resize(size); err != nil {
		return err
	}

	if actual := m.GetMaxEntries(); actual != size {
		return fmt.Errorf("map resize failed, expected %v, actual %v", size, actual)
	}

	return nil
}

func main() {
	/*get pidns of host */
	readPidNs()

	bpfModule, err := bpf.NewModuleFromFile("main.bpf.o")
	if err != nil {
		panic(err)
	}
	defer bpfModule.Close()
	if err := resizeMap(bpfModule, "events", 8192); err != nil {
		panic(err)
	}

	if err := bpfModule.BPFLoadObject(); err != nil {
		panic(err)
	}
	//refer-> https://github.com/mozillazg/hello-libbpfgo/blob/e8d9b9b8aacad1c8ee3d247ae7fa7457ed42f734/11-map-pin/main.go#L22
	exec, err := bpfModule.GetProgram("tracepoint__syscalls__sys_enter_execve")
	if err != nil {
		panic(err)
	}
	if _, err := exec.AttachTracepoint("syscalls", "sys_enter_execve"); err != nil {
		panic(err)
	}

	open, err := bpfModule.GetProgram("tracepoint__syscalls__sys_enter_openat")
	if err != nil {
		panic(err)
	}
	if _, err := open.AttachTracepoint("syscalls", "sys_enter_openat"); err != nil {
		panic(err)
	}

	eventsChannel := make(chan []byte)
	pb, err := bpfModule.InitRingBuf("events", eventsChannel)
	if err != nil {
		panic(err)
	}

	pb.Start()
	defer func() {
		pb.Stop()
		pb.Close()
	}()

	for {
		select {
		case e := <-eventsChannel:
			var cd cdata
			var dataBuffer *bytes.Buffer

			dataBuffer = bytes.NewBuffer(e)
			err = binary.Read(dataBuffer, binary.LittleEndian, &cd)

			switch cd.Type {
            case 1:
			if err != nil {
				log.Println(err)
				continue
			}
			if hostPidns != cd.PidNs{
				if getContainerId(cd.HostPid) != "" {
					log.Printf(`PidNs:%d,HostPid: %d, HostPpid: %d, Comm: %s, ContainerId: %s`,cd.PidNs,
					cd.HostPid, cd.HostPpid, cd.commName(), getContainerId(cd.HostPid))
					}
			}
			
			case 2:
				log.Printf(`This is open file probe`)
			}

		}
		
	}
}

func getContainerId(pid uint32) string {
	if pid == 0 {
		return ""
	}
	path := fmt.Sprintf("/proc/%d/cgroup", pid)
	file, err := os.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return ""
		}
		log.Printf("open file %s failed: %+v", path, err)
		return ""
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
    reContainerId := regexp.MustCompile("::/docker/([^/]+)")

    for scanner.Scan() {
        line := scanner.Text()
        if strings.Contains(line, "::/docker/") {
            matches := reContainerId.FindAllStringSubmatch(line, 1)
            if len(matches) > 0 {
                containerId := matches[0][1]
				return containerId
			}
	}
	}
	return ""
}